package com.tiancheng.trade.payment.channel.paypal;

import com.paypal.http.HttpResponse;
import com.paypal.orders.*;
import com.paypal.payments.CapturesRefundRequest;
import com.paypal.payments.RefundRequest;
import com.tiancheng.trade.commom.core.data.Result;
import com.tiancheng.trade.commom.core.utils.JsonUtil;
import com.tiancheng.trade.commom.core.utils.DateTimeUtil;
import com.tiancheng.trade.payment.channel.bo.PayResultBO;
import com.tiancheng.trade.payment.channel.bo.RefundOrderBO;
import com.tiancheng.trade.payment.channel.bo.RefundResultBO;
import com.tiancheng.trade.payment.enums.FlowStatusEnum;
import com.tiancheng.trade.payment.enums.PaypalStateEnum;
import com.tiancheng.trade.payment.util.PayPalClient;
import com.tiancheng.trade.payment.vo.api.PaypalCreateReqVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.text.ParseException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 *  paypal 支付渠道
 */
@Slf4j
@Service
public class PaypalPayChannel {

    @Value("${paypal.client.app}")
    private String clientId;
    @Value("${paypal.client.secret}")
    private String clientSecret;
    @Value("${paypal.mode}")
    private String mode;

//    @Value("${paypal.receiver.email}")
//    private String receiverEmail;


    private static final String CURRENCY = "USD";

    public static final String CAPTURE = "CAPTURE";// CAPTURE 和 AUTHORIZE
    /**
     * 该标签将覆盖PayPal网站上PayPal帐户中的公司名称
     */
    public static final String BRANDNAME = "GOYUNNAN";//待调整
    /**
     * LOGIN。当客户单击PayPal Checkout时，客户将被重定向到页面以登录PayPal并批准付款。
     * BILLING。当客户单击PayPal Checkout时，客户将被重定向到一个页面，以输入信用卡或借记卡以及完成购买所需的其他相关账单信息
     * NO_PREFERENCE。当客户单击“ PayPal Checkout”时，将根据其先前的交互方式将其重定向到页面以登录PayPal并批准付款，或重定向至页面以输入信用卡或借记卡以及完成购买所需的其他相关账单信息使用PayPal。
     * 默认值：NO_PREFERENCE
     */
    public static final String LANDINGPAGE = "NO_PREFERENCE";
    /**
     * CONTINUE。将客户重定向到PayPal付款页面后，将出现“ 继续”按钮。当结帐流程启动时最终金额未知时，请使用此选项，并且您想将客户重定向到商家页面而不处理付款。
     * PAY_NOW。将客户重定向到PayPal付款页面后，出现“ 立即付款”按钮。当启动结帐时知道最终金额并且您要在客户单击“ 立即付款”时立即处理付款时，请使用此选项。
     */
    public static final String USERACTION = "CONTINUE";
    /**
     * GET_FROM_FILE。使用贝宝网站上客户提供的送货地址。
     * NO_SHIPPING。从PayPal网站编辑送货地址。推荐用于数字商品
     * SET_PROVIDED_ADDRESS。使用商家提供的地址。客户无法在PayPal网站上更改此地址
     */
    public static final String SHIPPINGPREFERENCE = "NO_SHIPPING";

    /**
     * 创建订单的方法
     * @throws
     */
    public Result<HttpResponse<Order>> createOrder(PaypalCreateReqVO createReqVO)  {
        OrdersCreateRequest request = new OrdersCreateRequest();
        request.header("prefer","return=representation");
        request.requestBody(buildCreateOrderRequestBody(createReqVO));
        HttpResponse<Order> response = null;

        try {
            response = PayPalClient.client(mode, clientId, clientSecret).execute(request);
            log.info("创建支付：Status Code = {}, Status = {}, OrderID = {}, Intent = {}", response.statusCode(),
                    response.result().status(), response.result().id(), response.result().checkoutPaymentIntent());
            if (!String.valueOf(response.statusCode()).startsWith("2")) {
                return  Result.fail("paypal创建支付订单失败",response);
            }
        } catch (Exception e) {
            log.error("paypal创建支付订单失败，失败原因 {}", e.getMessage() );
            return  Result.fail("paypal创建支付订单失败",response);
        }
        return Result.success(response);
    }


    /**
     * 生成创建支付订单主体信息
     */
    private OrderRequest buildCreateOrderRequestBody(PaypalCreateReqVO createReqVO) {
        OrderRequest orderRequest = new OrderRequest();
        orderRequest.checkoutPaymentIntent(CAPTURE);

        ApplicationContext applicationContext = new ApplicationContext()
                .brandName(BRANDNAME)
                .landingPage(LANDINGPAGE)
                .cancelUrl(createReqVO.getCancelUrl())
                .returnUrl(createReqVO.getSuccessUrl())
                .userAction(USERACTION)
                .shippingPreference(SHIPPINGPREFERENCE);
        orderRequest.applicationContext(applicationContext);

        List<PurchaseUnitRequest> purchaseUnitRequests = new ArrayList<PurchaseUnitRequest>();
        @SuppressWarnings("serial")
        PurchaseUnitRequest purchaseUnitRequest = new PurchaseUnitRequest()
                .description((createReqVO.getDescription()!=null&&createReqVO.getDescription().length()>127)?createReqVO.getDescription().substring(0,127):createReqVO.getDescription())
                .customId("")
                .invoiceId("")
                .amountWithBreakdown(new AmountWithBreakdown()
                        .currencyCode(CURRENCY).value(createReqVO.getAmount()==null?"0.00":String.format("%.2f",new BigDecimal(createReqVO.getAmount())
                                .divide(new BigDecimal(100), 2, BigDecimal.ROUND_HALF_UP)))// //实收金额:  美分转为美元    value = itemTotal + shipping + handling + taxTotal + shippingDiscount;
                        .amountBreakdown(new AmountBreakdown()
                                .itemTotal(new Money().currencyCode(CURRENCY).value(createReqVO.getOrderAmount()==null?"0.00":String.format("%.2f",new BigDecimal(createReqVO.getOrderAmount())
                                        .divide(new BigDecimal(100), 2, BigDecimal.ROUND_HALF_UP)))) //  商品总价: 美分转为美元
                                .shipping(new Money().currencyCode(CURRENCY).value("0.00"))
                                .handling(new Money().currencyCode(CURRENCY).value("0.00"))
                                .taxTotal(new Money().currencyCode(CURRENCY).value(createReqVO.getTax()==null?"0.00":String.format("%.2f",new BigDecimal(createReqVO.getTax())
                                        .divide(new BigDecimal(100), 2, BigDecimal.ROUND_HALF_UP))))
                                .shippingDiscount(new Money().currencyCode(CURRENCY).value("0.00"))))
                .items(new ArrayList<Item>() {
                    {

                        for(int i=0;i<createReqVO.getSubPaypalOrders().size();i++){
                            PaypalCreateReqVO.SubOrderPaypalDTO subOrder=createReqVO.getSubPaypalOrders().get(i);
                            //paypal返回The value of a field is either too short or too long
                            add(new Item().name(subOrder.getProductName().length()>127?subOrder.getProductName().substring(0,127):subOrder.getProductName()) //字段过长，Minimum length: 1  Maximum length: 127
                                    .description(subOrder.getProductDetail().length()>127?subOrder.getProductDetail().substring(0,127):subOrder.getProductDetail())
                                    //paypal支付将 单价*数量 的金额和订单总金额做比较，这里unitAmount传的子订单金额(不是单价)，那quantity数量默认为1
                                    .unitAmount(new Money().currencyCode(CURRENCY)
                                            .value(subOrder.getAmount()==null?"0.00":String.format("%.2f",new BigDecimal(subOrder.getAmount())
                                                    .divide(new BigDecimal(100), 2, BigDecimal.ROUND_HALF_UP))))
                                    .quantity("1"));
                                   // .quantity(subOrder.getProductCount()==null?"0":String.valueOf(subOrder.getProductCount())));
                        }
                    }
                })
                /* 地址暂不处理
                .shippingDetail(new ShippingDetail()
                        .name(new Name().fullName("RATTA"))
                        .addressPortable(new AddressPortable()
                                .addressLine1("梅陇镇")
                                .addressLine2("集心路168号")
                                .adminArea2("闵行区")
                                .adminArea1("上海市")
                                .postalCode("20000")
                                .countryCode("CN")))*/;
        purchaseUnitRequests.add(purchaseUnitRequest);
        orderRequest.purchaseUnits(purchaseUnitRequests);
        return orderRequest;
    }



    /**
     * 确认支付：用户授权支付成功，进行扣款操作
     * orderId:  支付订单id， CreateOrder生成
     */
    public Result<HttpResponse<Order>> captureOrder(String orderId)  {
        OrdersCaptureRequest request = new OrdersCaptureRequest(orderId);
        request.requestBody(new OrderRequest());
        HttpResponse<Order> response = null;
        try {
            response = PayPalClient.client(mode, clientId, clientSecret).execute(request);
            log.info("确认支付：Status Code = {}, Status = {}, OrderID = {}", response.statusCode(), response.result().status(), response.result().id());
            if (!String.valueOf(response.statusCode()).startsWith("2")) {
                return  Result.fail("paypal确认支付失败",response);
            }
        } catch (Exception e) {
            log.error("paypal确认支付失败，失败原因 {}", e.getMessage() );
            return  Result.fail("paypal确认支付失败",response);
        }
        return Result.success(response);
    }


    /**
     * 申请退款
     */
    public Result<HttpResponse<com.paypal.payments.Refund>> refundOrder(RefundOrderBO refundBO) {
        log.info("调用paypal订单查询,订单号号：{}",refundBO.getTransactionId());
        Result<HttpResponse<com.paypal.orders.Order>> queryResult=this.getPaypalInfo(refundBO.getTransactionId());
        if(queryResult.getRet()!=0){
             return Result.fail(queryResult.getMsg(),null);
        }
        HttpResponse<com.paypal.orders.Order> queryResponse =queryResult.getData();
        String captureId = queryResponse.result().purchaseUnits().get(0).payments().captures().get(0).id();//paypal确认支付的流水号
        log.info("查询到的captureId为: {}", captureId);

        CapturesRefundRequest request = new CapturesRefundRequest(captureId);
        request.prefer("return=representation");
        request.requestBody(buildRundRequestBody(refundBO));
        HttpResponse<com.paypal.payments.Refund> response = null;
        try {
            response = PayPalClient.client(mode, clientId, clientSecret).execute(request);
            log.info("退款：Status Code = {}, Status = {}, RefundID = {}", response.statusCode(),
                    response.result().status(), response.result().id());
            String json = JsonUtil.toJsonString(response);
            log.info("refundOrder response body: {}", json);
            if (!String.valueOf(response.statusCode()).startsWith("2")) {
                return  Result.fail("paypal退款申请失败",response);
            }
        } catch (Exception e) {
            log.error("调用paypal退款申请失败，失败原因：{}",e.getMessage());
            return  Result.fail("paypal退款申请失败",response);
        }
        return Result.success(response);
    }


    /**
     * 创建退款请求体
     */
    public RefundRequest buildRundRequestBody(RefundOrderBO refundBO) {
        RefundRequest refundRequest = new RefundRequest();
        com.paypal.payments.Money money = new com.paypal.payments.Money();
        money.currencyCode(CURRENCY);
        money.value(refundBO.getAmount()==null?"0.00":String.format("%.2f",new BigDecimal(refundBO.getAmount())
                .divide(new BigDecimal(100), 2, BigDecimal.ROUND_HALF_UP)));
        refundRequest.amount(money);
        refundRequest.invoiceId(null);//发票号，暂时传null,   创建支付后返回链接有此字段
        refundRequest.noteToPayer(refundBO.getReason());//退款原因
        return refundRequest;
    }

    /**
     *  paypal支付订单查询
     * @param paypalOrderId
     * @return
     */
    public Result<HttpResponse<com.paypal.orders.Order>> getPaypalInfo(String paypalOrderId){
        log.info("paypal支付订单查询,支付订单号号：{}",paypalOrderId);
        OrdersGetRequest ordersGetRequest = new OrdersGetRequest(paypalOrderId);//支付返回的渠道单号
        HttpResponse<com.paypal.orders.Order> ordersGetResponse = null;
        try {
            ordersGetResponse = PayPalClient.client(mode, clientId, clientSecret).execute(ordersGetRequest);
            log.info("查询：Status Code = {}, Status = {}", ordersGetResponse.statusCode(),
                    ordersGetResponse.result().status());
            if (!String.valueOf(ordersGetResponse.statusCode()).startsWith("2")) {
                return  Result.fail("paypal支付订单查询失败",ordersGetResponse);
            }
        } catch (Exception e) {
            log.error("paypal支付订单查询失败，失败原因：{}",e.getMessage());
            return  Result.fail("paypal支付订单查询失败",ordersGetResponse);
        }
        return Result.success(ordersGetResponse);
    }


    /**
     * ipn回调支付结果数据解析
     * @param paramsMap
     * @return
     */
    public PayResultBO payResult(Map<String, Object> paramsMap) {
        log.info("paypal回调数据为，{}",paramsMap.toString());
        String outTradeNo = (String)paramsMap.get("invoice");
        String paymentStatus = (String)paramsMap.get("payment_status");
        String amount = (String)paramsMap.get("mc_gross");
        String currency = (String)paramsMap.get("mc_currency");
        String paymentId = (String)paramsMap.get("txn_id"); // paypal支付流水号确认支付接口已存入transaction_id 字段
        String parentPaymentId = (String)paramsMap.get("parent_txn_id");
        String payerId = (String)paramsMap.get("payer_id");
        String payTime = (String)paramsMap.get("payment_date");
        log.info("商家订单号 = {},订单状态 = {},金额 = {},币种 = {},流水号 = {},父流水号 = {},买家id = {},支付时间 = {}",
                outTradeNo,paymentStatus,amount,currency,paymentId,parentPaymentId,payerId,payTime);
        LocalDateTime paymentTime=LocalDateTime.now();
        //paypal回调的付款时间 20:17:38 Oct 27, 2021 PDT 转为北京时间
        if(payTime!=null&&(payTime.endsWith("PDT")||payTime.endsWith("PST"))){
            try {
                paymentTime= DateTimeUtil.PDTorPST2Beijing(payTime);
                log.info("转换后支付时间 = {}", paymentTime);
            } catch (ParseException e) {
                log.error("paypal回调支付时间转换异常，失败原因：{}",e.getMessage());
            }
        }
//        if (!receiverEmail.equals((String) paramsMap.get("receiver_email"))) {
//            log.info("FAIL = 商户id错误, outTradeNo = {}", outTradeNo);
//        }
        String payState="";
        if(PaypalStateEnum.IPN_COMPLETED.getCode().equals(paymentStatus)) {
            log.info("支付成功,状态为="+paymentStatus);
            payState= FlowStatusEnum.SUCCESS.getCode();
        }
        if(PaypalStateEnum.IPN_FAILED.getCode().equals(paymentStatus)) {
            log.info("支付失败,状态为="+paymentStatus);
            payState= FlowStatusEnum.FAIL.getCode();
        }
        if(PaypalStateEnum.IPN_PENDING.getCode().equals(paymentStatus) && StringUtils.isEmpty(parentPaymentId)) {
            String pendingReason = String.valueOf(paramsMap.get("pending_reason"));
            log.info("订单支付成功,状态为=PENDING，产生此状态的原因是 {}", pendingReason );
        }

        PayResultBO payResultBO = new PayResultBO();
        payResultBO.setStatus(payState);
        payResultBO.setUid(payerId); //用户id
        payResultBO.setOrderId(paymentId);//交易系统的支付流水号: paypal支付流水号
        payResultBO.setTransactionId(paymentId);
        payResultBO.setTradeNo(String.valueOf(""));//收单机构的单号,回调时未返回，流水表有存
        payResultBO.setAmount(new BigDecimal(amount).
                multiply(new BigDecimal(100)).intValue());//实际支付金额，回调的结果是美元，转成美分
        payResultBO.setMetadata("");
        // 根据实际情况修改优惠金额
        payResultBO.setCouponAmount(0);
        payResultBO.setPayTime(paymentTime);
        return payResultBO;
    }

    /**
     * ipn回调退款结果数据解析
     * @param paramsMap
     * @return
     */
    public RefundResultBO refundResult(Map<String, Object> paramsMap) {
        /**
         * 样例：
         * mc_gross=-56.99&protection_eligibility=Eligible&item_number1=&payer_id=JWYNZNG3WX328&payment_date=19:17:39 Sep 12, 2021 PDT&payment_status=Refunded&charset=gb2312&mc_shipping=0.00&mc_handling=0.00&first_name=YAN&mc_fee=-1.94&notify_version=3.9&reason_code=refund&custom=&business=gaoyan@yntengyun.com&mc_handling1=0.00&verify_sign=Aj8z7zkOdcDf.OxlaNWJp-eFvyu9AtHbEpKDWapUCylEozGHy4qR4V2W&payer_email=gy15176212857@163.com&mc_shipping1=0.00&tax1=0.00&parent_txn_id=5UP28149XF4195729&txn_id=4CY05620DF5483418&payment_type=instant&last_name=GAO&item_name1=PayPal支付下订单4&receiver_email=gaoyan@yntengyun.com&payment_fee=-1.94&shipping_discount=0.00&quantity1=1&insurance_amount=0.00&receiver_id=WF83L3VGAX9ZY&discount=0.00&mc_gross_1=56.99&mc_currency=USD&residence_country=CN&test_ipn=1&shipping_method=Default&transaction_subject=PayPal支付下订单4&payment_gross=-56.99&ipn_track_id=16884564733de
         */
        log.info("paypal支付退款回调数据为，{}",paramsMap.toString());
        String outTradeNo = (String)paramsMap.get("invoice");
        String paymentStatus = (String)paramsMap.get("payment_status");
        String amount = (String)paramsMap.get("mc_gross");
        String currency = (String)paramsMap.get("mc_currency");
        String paymentId = (String)paramsMap.get("txn_id"); // paypal退款流水号
        String parentPaymentId = (String)paramsMap.get("parent_txn_id");// paypal支付流水号
        String payerId = (String)paramsMap.get("payer_id");
        String payTime = (String)paramsMap.get("payment_date"); //退款时间
        log.info("商家订单号 = {},订单状态 = {},金额 = {},币种 = {},流水号(退款流水) = {},父流水号(支付流水) = {},买家id = {},退款时间 = {}",
                outTradeNo,paymentStatus,amount,currency,paymentId,parentPaymentId,payerId,payTime);
        LocalDateTime refundTime=LocalDateTime.now();
        //paypal回调的退款时间 00:33:23 Oct 27, 2021 PDT 转为北京时间
        if(payTime!=null&&(payTime.endsWith("PDT")||payTime.endsWith("PST"))){
            try {
                refundTime=DateTimeUtil.PDTorPST2Beijing(payTime);
                log.info("转换后退款时间 = {}", refundTime);
            } catch (ParseException e) {
                log.error("paypal回调退款时间转换异常，失败原因：{}",e.getMessage());
            }
        }
//        if (!receiverEmail.equals((String) paramsMap.get("receiver_email"))) {
//            log.info("FAIL = 商户id错误, outTradeNo = {}", outTradeNo);
//        }
        String payState="";
        if(PaypalStateEnum.IPN_REFUNDED.getCode().equals(paymentStatus)) {
            log.info("退款成功,状态为="+paymentStatus);
            payState=FlowStatusEnum.SUCCESS.getCode();
        }
        RefundResultBO refundResultBO = new RefundResultBO();
        refundResultBO.setStatus(payState);
        //refundResultBO.setRefundId("");//退款单号（交易系统的退款流水号）
        refundResultBO.setTransactionId(paymentId);//支付渠道网关返回的退款单号,paypal 退款流水号
        refundResultBO.setTradeNo(parentPaymentId);//收单机构的退款单号： 这里用来存放paypal退款交易号对应的支付流水号
        refundResultBO.setAmount(Math.abs(new BigDecimal(amount).
                multiply(new BigDecimal(100)).intValue()) );//回调的金额为负数，转为正数的美分
        refundResultBO.setRefundAmount(refundResultBO.getAmount());
        refundResultBO.setRefundTime(refundTime);
        return refundResultBO;
    }

}
